package com.pkk.spring.cloud.service.gateway.route;

import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.pkk.spring.cloud.config.nacos.properties.NacosProperties;
import com.pkk.spring.cloud.config.nacos.properties.NacosProperties.NacosConfigProperties;
import com.pkk.spring.cloud.service.gateway.config.GatewayNacosConfigListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.data.redis.core.RedisTemplate;
import reactor.core.publisher.Mono;

/**
 * 路由redis的仓库
 *
 * @author peikunkun
 * @version V1.0
 * @date 2021-01-15 18:19
 **/
@Slf4j
@EnableConfigurationProperties
@AutoConfigureBefore(GatewayAutoConfiguration.class)
public class InRedisRouteDefinitionRepository extends AdviceRouteDefinitionRepository {

  @Autowired
  private RedisTemplate redisTemplate;
  @Autowired
  private NacosProperties nacosProperties;
  @Autowired
  private GatewayNacosConfigListener gatewayNacosConfigListener;

  /**
   * 默认的路由后缀
   */
  protected String DEFAULT_ROUTE_SUFFIX = "ROUTE";


  /**
   * 路由配置持久化key
   */
  private static final String DEFAULT_ROUTE_CONFIG_REPOSITORY_KEY = "route";


  /**
   * 获取路由定义信息KEY
   *
   * @return java.lang.String
   * @Param
   * @author peikunkun
   * @date 2021/1/19 0019 下午 6:13
   * @since
   */
  public String getRouteDefinitionKey() {
    return this.getClass().getName().concat(DEFAULT_ROUTE_SUFFIX);
  }


  /**
   * 初始化动态路由
   *
   * @author peikunkun
   * @date 2021/1/23 0023 上午 10:43
   */
  @Override
  public void init() throws Exception {
    final NacosConfigProperties redis = nacosProperties.getDynamic().get(DEFAULT_ROUTE_CONFIG_REPOSITORY_KEY);
    ConfigService configService = null;
    try {
      configService = gatewayNacosConfigListener
          .getConfig(gatewayNacosConfigListener.getConfigId(redis.getServerAddr(), redis.getNamespace()));
      String configInfo = gatewayNacosConfigListener
          .getConfig(configService, redis.getGroup(), redis.getDataId(), DEFAULT_ROUTE_CONFIG_REPOSITORY_KEY);
      if (StringUtils.isBlank(configInfo)) {
        log.warn("Gateway routing information not obtained, please check the service!");
      }
      List<RouteDefinition> definitionList = JSONUtil.toList(configInfo, RouteDefinition.class);
      for (RouteDefinition definition : definitionList) {
        log.info("update routing information: [{}]", definition.toString());
        save(Mono.just(definition));
      }
    } catch (Exception e) {
      log.error("初始化网关路由时发生错误", e);
      throw e;
    }
    dynamicRouteByNacosListener(configService, redis.getDataId(), redis.getGroup());
  }

  /**
   * 默认执行销毁操作
   *
   * @return void
   * @Param args
   * @author peikunkun
   * @date 2021/1/23 0023 下午 4:23
   * @since
   */
  @Override
  public void destroy() throws Exception {
    try {
      redisTemplate.delete(getRouteDefinitionKey());
    } catch (Exception e) {
      log.error("[删除路由缓存失败]-此操作失败有可能导致路由错误", e);
      throw e;
    }
  }

  /**
   * 监听Nacos下发的动态路由配置
   *
   * @param dataId
   * @param group
   */
  public void dynamicRouteByNacosListener(ConfigService configService, String dataId, String group) {
    try {
      configService.addListener(dataId, group, new Listener() {
        @SneakyThrows
        @Override
        public void receiveConfigInfo(String configInfo) {
          final String config = gatewayNacosConfigListener.getConfig(configInfo, DEFAULT_ROUTE_CONFIG_REPOSITORY_KEY);
          List<RouteDefinition> definitionList = JSONUtil.toList(config, RouteDefinition.class);
          update(definitionList);
        }

        @Override
        public Executor getExecutor() {
          log.info("getExecutor\n\r");
          return null;
        }
      });
    } catch (NacosException e) {
      log.error("[此配置错误将可能导致动态配置失效]-从nacos接收动态路由配置出错!!!", e);
    }
  }


  /**
   * 获取路由定义信息
   *
   * @return java.util.List<org.springframework.cloud.gateway.route.RouteDefinition>
   * @Param
   * @author peikunkun
   * @date 2021/1/23 0023 上午 10:38
   * @since
   */
  @Override
  protected List<RouteDefinition> getRouteDefinitionList() {
    List<RouteDefinition> routeDefinitions = new ArrayList<>();
    redisTemplate.opsForHash().values(getRouteDefinitionKey()).stream()
        .forEach(routeDefinition -> routeDefinitions
            .add(JSONUtil.toBean(routeDefinition.toString(), RouteDefinition.class)));
    return routeDefinitions;
  }


  /**
   * 保存路由
   *
   * @return reactor.core.publisher.Mono<java.lang.Void>
   * @Param route
   * @author peikunkun
   * @date 2021/1/23 0023 下午 5:32
   * @since
   */
  @Override
  public Mono<Void> save(Mono<RouteDefinition> route) {
    final Mono<Void> id_may_not_be_empty = route.flatMap(r -> {
      if (StringUtils.isEmpty(r.getId())) {
        return Mono.error(new IllegalArgumentException("id may not be empty"));
      }
      redisTemplate.opsForHash().put(getRouteDefinitionKey(), r.getId(), JSONUtil.toJsonStr(r));
      return Mono.empty();
    });
    id_may_not_be_empty.subscribe();
    //通知
    super.notifyChanged();
    return id_may_not_be_empty;
  }

  /**
   * 删除
   *
   * @return reactor.core.publisher.Mono<java.lang.Void>
   * @Param routeId
   * @author peikunkun
   * @date 2021/1/23 0023 下午 5:32
   * @since
   */
  @Override
  public Mono<Void> delete(Mono<String> routeId) {
    final Mono<Void> objectMono = routeId.flatMap(id -> {
      final String routeDefinitionKey = getRouteDefinitionKey();
      final Long delete = redisTemplate.opsForHash().delete(routeDefinitionKey, id);
      return Mono.empty();
    });
    //生效
    objectMono.subscribe();
    //通知
    super.notifyChanged();
    return objectMono;
  }

  /**
   * 更新路由
   *
   * @param definitions
   * @return boolean
   * @Param definition
   * @author peikunkun
   * @date 2021/1/22 0022 下午 6:05
   * @since
   */
  @Override
  public boolean update(List<RouteDefinition> definitions) {
    definitions.stream().forEach(definition -> {
      this.delete(Mono.just(definition.getId())).subscribe();
      this.save(Mono.just(definition)).subscribe();
    });
    //通知
    super.notifyChanged();
    return true;
  }
}
